﻿namespace Sogeti.QueryWrapper
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml;
    using Microsoft.TeamFoundation.Client;
    using Microsoft.TeamFoundation.Server;
    using Microsoft.TeamFoundation.WorkItemTracking.Client;
    using Sogeti.VSExtention;

    public delegate void ActionNotifyCallbackDelegate(double value, string currentOperation);

    public enum QueryITemType
    {
        Folder,
        FlatQuery,
        WILinksQuery,
        TreeQuery
    }

    public class ActionNotifyCallback
    {
        public ActionNotifyCallbackDelegate _delegate;
        public double start;
        public double range;

        public void DoCallback(double value, string operation)
        {
            if (_delegate != null)
            {
                _delegate(start + (value * range), operation);
            }
        }
    }

   public class WIQLQueryItems
   {
       public string QueryTxt;

       public WIQLQueryItems()
       {
           Childs = new List<WIQLQueryItems>();
       }

       public string Name { get; set; }

       public string Path { get; set; }

       public Guid QueryGuid { get; set; }

       public QueryITemType ItemType { get; set; }

       public List<WIQLQueryItems> Childs { get; set; }
   }

    public class WiqlFolder
    {
        public WiqlFolder()
        {
            queries = new Dictionary<string, string>();
            subFolders = new List<WiqlFolder>();
        }

        public string Name { get; set; }

        public string Path { get; set; }

        public Dictionary<string, string> queries { get; set; }

        public List<WiqlFolder> subFolders { get; set; }
    }

    public class TPQueryWrapper
    {
        private TfsTeamProjectCollection tpCollection;
        private WorkItemStore wiStore;
        private QueryHierarchy qh;
        private CommonStructureWrapper css;
        private string tpName;
        private string tqFolderName;

        public TPQueryWrapper(string tpCollectionUrl, string teamProjectName)
        {
            tpCollection = new TfsTeamProjectCollection(new Uri(tpCollectionUrl));
            wiStore = (WorkItemStore)tpCollection.GetService(typeof(WorkItemStore));
            qh = wiStore.Projects[teamProjectName].QueryHierarchy;
            css = new CommonStructureWrapper(tpCollectionUrl);
            tpName = teamProjectName;
            tqFolderName = null;
        }

        public TPQueryWrapper(TeamExplorerIntergator teamExplorer)
        {
            if (teamExplorer != null)
            {
                tpCollection = teamExplorer.tpCollection;
                tpName = teamExplorer.tpName;

                wiStore = (WorkItemStore)tpCollection.GetService(typeof(WorkItemStore));
                qh = wiStore.Projects[tpName].QueryHierarchy;
                css = new CommonStructureWrapper(tpCollection);
            }
        }

        public string TeamQueryFolderName
        {
            get
            {
                if (tqFolderName == null)
                { 
                    tqFolderName = "Team Queries";
                    if (FindFolder(tqFolderName) == null)
                    {
                        tqFolderName = "Shared Queries";
                    }
                }

                return tqFolderName;
           }
        }


        public List<string> GetUsedIterationsInFolder(WiqlFolder fld)
        {
            List<string> lst = new List<string>();

            foreach (KeyValuePair<string, string> q in fld.queries)
            {
                if (q.Value.Contains("System.IterationPath"))
                {
                    string s = ExtractIterationFromWIQLString(q.Value);

                    string path = @"\Iteration\" + s;

                    lst.Add(path);

                    break;
                }
            }

            return lst;
        }

        public WiqlFolder FindFolderWithFilter(string filter, bool allQueriesFiltered)
        {
            foreach (QueryFolder fld in qh)
            {
                WiqlFolder wiqlFld = FindFolderWithFilter(fld, filter, allQueriesFiltered);
                if (wiqlFld != null)
                {
                    return wiqlFld;
                }
            }

            return null;
        }

        public WiqlFolder FindFolderWithFilter(QueryFolder fld, string filter, bool allQueriesFiltered)
        {
            bool filterFound = false;
            bool allFiltered = true;

            WiqlFolder wiqlFld = new WiqlFolder { Name = fld.Name, Path = fld.Path };

            foreach (QueryItem itm in fld)
            {
                if (itm.GetType() == typeof(QueryFolder))
                {
                    return FindFolderWithFilter((QueryFolder)itm, filter, allQueriesFiltered);
                }
                else
                {
                    string sWIql = ((QueryDefinition)itm).QueryText;
                    wiqlFld.queries.Add(itm.Name, sWIql);

                    if (sWIql.IndexOf(filter) >= 0)
                    {
                        filterFound = true;
                    }
                    else
                    {
                        allFiltered = false;
                    }
                }
            }

            if (filterFound && allQueriesFiltered && allFiltered)
            {
                return wiqlFld;
            }
            else
            {
                return null;
            }
        }

        public WiqlFolder FindWiqlFolder(string path)
        {
            QueryFolder fld = FindFolder(path);

            WiqlFolder wiqlFld = new WiqlFolder { Name = fld.Name, Path = fld.Path };

            foreach (QueryItem itm in fld)
            {
                if (itm.GetType() == typeof(QueryFolder))
                {
                    //What to do if I ind a folder ?
                    //return FindFolderWithFilter((QueryFolder)itm, filter, allQueriesFiltered);
                }
                else
                {
                    string sWIql = ((QueryDefinition)itm).QueryText;
                    wiqlFld.queries.Add(itm.Name, sWIql);
                }
            }

            return wiqlFld;
        }

        public QueryFolder FindFolder(string path, QueryFolder fld)
        {
            string fldName = path;

            if (path.IndexOf(@"/") > 0)
            {
                fldName = path.Substring(0, path.IndexOf(@"/"));
            }

            if (fld.Contains(fldName))
            {
                if (path.IndexOf(@"/") > 0)
                {
                    return FindFolder(path.Substring(path.IndexOf(@"/") + 1), (QueryFolder)fld[fldName]);
                }
                else
                {
                    return (QueryFolder)fld[fldName];
                }
            }
            else
            {
                return null;
            }
        }

        public QueryFolder FindFolder(string path)
        {
            path = path.Replace(qh.Name + @"/", string.Empty);
            return FindFolder(path, qh);
        }

        public void SaveQueryFolder(WiqlFolder wiqlFLd)
        {
            qh.Project.Store.RefreshCache(); // Force refresh on Iterations & areas to avoid error in save
            qh.Refresh();

            string path = wiqlFLd.Path.Substring(wiqlFLd.Path.IndexOf(@"/") + 1);

            QueryFolder fld = FindFolder(path);
            if (fld == null)
            {
                QueryFolder fldParent = FindFolder(path.Substring(0, path.LastIndexOf(@"/")));

                fld = new QueryFolder(wiqlFLd.Name);
                fldParent.Add(fld);
            }

            foreach (KeyValuePair<string, string> qry in wiqlFLd.queries)
            {
                QueryDefinition itmTarget = null;
                if (fld.Contains(qry.Key))
                {
                    itmTarget = (QueryDefinition)fld[qry.Key];
                    itmTarget.QueryText = qry.Value;
                }
                else
                {
                    itmTarget = new QueryDefinition(qry.Key, qry.Value);
                    fld.Add(itmTarget);
                }
            }

            qh.Save();
            qh.Refresh();
            wiStore.RefreshCache();
        }

        public WiqlFolder ReadQueryStructure2(string path)
        {
            WiqlFolder fld = new WiqlFolder() { Name = TeamQueryFolderName, Path = path };
            return fld;
        }

        public WIQLQueryItems ReadQueryStructure(string path)
        {
            WIQLQueryItems fldRoot = new WIQLQueryItems() { Name = "Work Item Queries", Path = @"\" };

            WIQLQueryItems fld = new WIQLQueryItems() { Name = TeamQueryFolderName, Path = path };
            FetchItems(ref fld);

            fldRoot.Childs.Add(fld);
            return fldRoot;
        }

        public WIQLQueryItems FetchItems(ref WIQLQueryItems fld, QueryFolder iParent = null)
        {
            if (iParent == null)
            {
                iParent = FindFolder(fld.Path);
            }

            foreach (QueryItem itm in iParent)
            {
                WIQLQueryItems i = new WIQLQueryItems() { Name = itm.Name, ItemType = GetQueryITemType(itm), QueryGuid = itm.Id };
            
                fld.Childs.Add(i);
                if (i.ItemType == QueryITemType.Folder)
                {
                    FetchItems(ref i, itm as QueryFolder);
                }
            }
                    
            return fld;
        }

        public QueryITemType GetQueryITemType(QueryItem i)
        {
            if (i is QueryFolder)
            {
                return QueryITemType.Folder;
            }
            else
            {
                switch (((QueryDefinition)i).QueryType)
                {
                    case QueryType.List:
                        return QueryITemType.FlatQuery;
                    case QueryType.OneHop:
                        return QueryITemType.WILinksQuery;
                    case QueryType.Tree:
                        return QueryITemType.TreeQuery;
                }
            }

            return QueryITemType.FlatQuery;
        }
       
        public WiqlFolder FetchFolder2(ref WiqlFolder fld)
        {
            fld.subFolders = ListFolders(fld.Path);
            for (int i = 0; i < fld.subFolders.Count; i++)
            {
                WiqlFolder f = fld.subFolders[i];
                fld.subFolders[i] = f;
            }

            return fld;
        }

        public List<WiqlFolder> ListFolders(string path)
        {
            List<WiqlFolder> lst = new List<WiqlFolder>();

            QueryFolder fld = FindFolder(path);
            if (fld != null)
            {
                foreach (QueryItem itm in fld)
                {
                    if (itm.GetType() == typeof(QueryFolder))
                    {
                        lst.Add(new WiqlFolder() { Name = itm.Name, Path = itm.Path });
                        lst.AddRange(ListFolders(itm.Path));
                    }
                }
            }

            return lst;
        }

        public List<WorkItem> SelectDistinct(List<WorkItem> lst)
        {
            List<int> keys = new List<int>();
            List<WorkItem> wiLst = new List<WorkItem>();
            foreach (WorkItem wi in lst)
            {
                if (!keys.Contains(wi.Id))
                {
                    wiLst.Add(wi);
                    keys.Add(wi.Id);
                }
            }

            return wiLst;
        }

        #region "oldstuf"
        public void CopyFolder(string sourceFolder, string targetFolder, string search, string replace)
        {
            foreach (QueryFolder fld in qh)
            {
                CopyFolder(fld, sourceFolder, targetFolder, search, replace);
            }
        }

        public void CopyFolder(QueryFolder fldRoot, string sourceFolder, string targetFolder, string search, string replace)
        {
            foreach (QueryItem fld in fldRoot)
            {
                if (fld.GetType() == typeof(QueryFolder))
                {
                    if (fld.Name == sourceFolder)
                    {
                        QueryFolder fldTarget = new QueryFolder(targetFolder);
                        fld.Parent.Add(fldTarget);

                        CopyFolder((QueryFolder)fld, fldTarget, search, replace);
                        qh.Save();
                    }
                }
            }
        }

        #endregion

        internal List<string> ReplaceIterationinFolder(ref WiqlFolder currentFolder, string oldIteration, string newIteration, ActionNotifyCallback callback = null)
        {
            List<string> lst = new List<string>();

            oldIteration = ConvertIterationPathToWIQLExpr(oldIteration);
            newIteration = ConvertIterationPathToWIQLExpr(newIteration);

            if (callback == null)
            {
                callback = new ActionNotifyCallback();
            }

            for (int i = 0; i < currentFolder.queries.Count; i++)
            {
                KeyValuePair<string, string> q = currentFolder.queries.ElementAt(i);

                callback.DoCallback(((double)i) / currentFolder.queries.Count, "Searching query " + q.Key + " " + i.ToString() + " of " + currentFolder.queries.Count.ToString());

                if (q.Value.Contains("System.IterationPath"))
                {
                    string sNewQuery = q.Value.Replace(oldIteration, newIteration);
                    if (q.Value != sNewQuery)
                    {
                        callback.DoCallback(((double)i) / currentFolder.queries.Count, "Replaced Iteration path in query " + q.Key);
                        lst.Add(q.Key);
                    }

                    currentFolder.queries[q.Key] = sNewQuery;
                }
            }

            return lst;
        }

        internal List<WorkItem> SelectWorkItemsFromQuery(string nameWorkItemType, Guid queryGuid)
        {
            QueryDefinition qd = qh.Find(queryGuid) as QueryDefinition;

            Dictionary<string, string> d = new Dictionary<string, string>();
            d.Add("project", qh.Project.Name);

            Query q = new Query(wiStore, qd.QueryText, d);
            List<WorkItem> lst = new List<WorkItem>();

            switch (qd.QueryType)
            {
                case QueryType.List:
                    {
                        foreach (WorkItem wi in q.RunQuery())
                        {
                            if (wi.Type.Name == nameWorkItemType)
                            {
                                lst.Add(wi);
                            }
                        }
                    }

                    break;
                case QueryType.OneHop:
                    {
                        foreach (WorkItemLinkInfo wil in q.RunLinkQuery())
                        {
                            if (wil.SourceId > 0)
                            {
                                WorkItem wi = wiStore.GetWorkItem(wil.TargetId);
                                if (wi.Type.Name == nameWorkItemType)
                                {
                                    lst.Add(wi);
                                }
                            }

                            if (wil.SourceId > 0)
                            {
                                WorkItem wi = wiStore.GetWorkItem(wil.SourceId);
                                if (wi.Type.Name == nameWorkItemType)
                                {
                                    lst.Add(wi);
                                }
                            }
                        }
                    }

                    break;
                case QueryType.Tree:
                    {
                        foreach (WorkItemLinkInfo wil in q.RunLinkQuery())
                        {
                            WorkItem wi = wiStore.GetWorkItem(wil.TargetId);
                            if (wi.Type.Name == nameWorkItemType)
                            {
                                lst.Add(wi);
                            }

                            wi = wiStore.GetWorkItem(wil.SourceId);
                            if (wi.Type.Name == nameWorkItemType)
                            {
                                lst.Add(wi);
                            }
                        }
                    }

                    break;
            }

            return lst;
        }

        private static void CopyFolder(QueryFolder fld, QueryFolder fldTarget, string search, string replace)
        {
            foreach (QueryItem itm in fld)
            {
                if (itm.GetType() == typeof(QueryFolder))
                {
                    QueryFolder newFolder = new QueryFolder(itm.Name);
                    fldTarget.Add(newFolder);
                    CopyFolder((QueryFolder)itm, newFolder, search, replace);
                }
                else
                {
                    string sWIql = ((QueryDefinition)itm).QueryText;
                    if (search != string.Empty && replace != string.Empty)
                    {
                        sWIql = sWIql.Replace(search, replace);
                    }

                    QueryDefinition itmTarget = new QueryDefinition(itm.Name, sWIql);
                    fldTarget.Add(itmTarget);
                }
            }
        }

        private string ExtractIterationFromWIQLString(string sWiql)
        {
            string s = string.Empty;
            if (sWiql.ToUpper().Contains("WHERE"))
            {
                sWiql = sWiql.Substring(sWiql.ToUpper().IndexOf("WHERE"));
                if (sWiql.Contains("System.IterationPath"))
                {
                    s = sWiql.Substring(sWiql.IndexOf("System.IterationPath"));
                    if (s.ToUpper().Contains(" AND "))
                    {
                        s = s.Substring(0, s.ToUpper().IndexOf(" AND "));
                    }

                    if (s.ToUpper().Contains(")AND "))
                    {
                        s = s.Substring(0, s.ToUpper().IndexOf(")AND"));
                    }

                    if (s.ToUpper().Contains(" OR "))
                    {
                        s = s.Substring(0, s.ToUpper().IndexOf(" OR "));
                    }

                    if (s.ToUpper().Contains(")OR"))
                    {
                        s = s.Substring(0, s.ToUpper().IndexOf(")OR"));
                    }

                    s = s.Substring(s.IndexOf("'" + tpName + @"\") + 1);
                    s = s.Substring(0, s.IndexOf("'"));
                }
            }

            return s;
        }

        private string FindFirstNode(XmlNode startNode)
        {
            if (startNode.HasChildNodes)
            {
                return FindFirstNode(startNode.ChildNodes[0]);
            }
            else
            {
                return startNode.Attributes["Path"].Value.ToString();
            }
        }
        
        private string ConvertIterationPathToWIQLExpr(string oldIteration)
        {
            if (oldIteration.Contains(tpName))
            {
                if (!oldIteration.StartsWith(tpName))
                {
                    oldIteration = oldIteration.Substring(oldIteration.IndexOf(tpName));
                }

                if (oldIteration.Contains(tpName + @"\Iteration\"))
                {
                    oldIteration = oldIteration.Replace(tpName + @"\Iteration\", tpName + @"\");
                }
            }
            else
            {
                throw new Exception("Iteration path in wrong format ");
            }

            return oldIteration;
        }
    }
}